-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local class = require 'mc.class'
local port_mgmt = require 'device.class.nic_mgmt.port_mgmt'
local c_optical_module = require 'device.class.optical_module'
local om_init = require 'device.class.nic_mgmt.om.om_init'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local comm_defs = require 'device.class.nic_mgmt.comm_defs'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_factory = require 'mc.orm.factory'

local optical_module_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local prop_table = {
    Manufacturer = 'Manufacturer',
    PartNumber = 'PartNumber',
    SerialNumber = 'SerialNumber',
    ProductionDate = 'ProductionDate',
    TransceiverType = 'TransceiverType',
    FiberConnectionType = 'FiberConnectionType',
    WaveLengthNanometer = 'WaveLengthNanometer',
    Identifier = 'Identifier',
    ConnectorType = 'ConnectorType',
    MediumType = 'MediumType',
    RxLossState = 'RxLossState',
    TxLossState = 'TxLossState',
    TxFaultState = 'TxFaultState',
    Presence = 'Presence',
    TransceiverState = 'TransceiverState',
    ChannelNum = 'ChannelNum',
    RxSNR = 'RxSNR',
    HostSNR = 'HostSNR',
    MediaSNR = 'MediaSNR',
    Accessible = 'Accessible',
    RXInputPowerMilliWatts = 'RXInputPowerMilliWatts',
    RXPowerUpperThresholdCritical = 'RXUpperThresholdCritical',
    RXPowerLowerThresholdCritical = 'RXLowerThresholdCritical',
    TXOutputPowerMilliWatts = 'TXOutputPowerMilliWatts',
    TXPowerUpperThresholdCritical = 'Power_TXUpperThresholdCritical',
    TXPowerLowerThresholdCritical = 'Power_TXLowerThresholdCritical',
    TXBiasCurrentMilliAmps = 'TXBiasCurrentMilliAmps',
    TXBcUpperThresholdCritical = 'BC_TXUpperThresholdCritical',
    TXBcLowerThresholdCritical = 'BC_TXLowerThresholdCritical',
    TemperatureCelsius = 'ReadingCelsius',
    TemperatureLowerThresholdCritical = 'TemperatureLowerThresholdCritical',
    TemperatureUpperThresholdCritical = 'TemperatureUpperThresholdCritical',
    SupplyVoltage = 'SupplyVoltage',
    VoltageUpperThresholdCritical = 'VoltageUpperThresholdCritical',
    VoltageLowerThresholdCritical = 'VoltageLowerThresholdCritical',
    RxLossOfLock = 'RxLossOfLock',
    TxLossOfLock = 'TxLossOfLock',
    LaserRuntimeSeconds = 'LaserRuntimeSeconds',
    LaserTemperatureCelsius = 'LaserTemperatureCelsius',
    PowerOnCount = 'PowerOnCount',
    UptimeSeconds = 'UptimeSeconds',
    PowerStatus = 'PowerStatus',
    OdspDieTemperatureCelsius = 'OdspDieTemperatureCelsius',
    OdspHighTempRuntimeSeconds = 'OdspHighTempRuntimeSeconds',
    SelfTestStatus = 'SelfTestStatus',
    MediaInterfaceFaultStatus = 'MediaInterfaceFaultStatus',
    HostInterfaceFaultStatus = 'HostInterfaceFaultStatus',
    RxLoS = 'RxLoS',
    TxLoS = 'TxLoS',
    TxFault = 'TxFault',
    PRBSTestSupported = 'PRBSTestSupported',
    FiberDirtyDetected = 'FiberDirtyDetected',
    FiberLoosenessDetected = 'FiberLoosenessDetected',
    FaultState = 'FaultState'
}

-- 处理特殊的属性映射逻辑
local function handle_special_property_mapping(interface, prop, orm_obj, value)
    if prop == 'TransmissionDistance' and interface == comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE then
        orm_obj['TransmissionDistance'] = tostring(value)
    end
end

local function listen_device_obj_property_change(bus, sig_slot, device_path, orm_obj)
    log:notice('listen_device_obj_property_change start, optical_device_path is %s', device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            pcall(function()
                if prop_table[k] then
                    orm_obj[prop_table[k]] = v:value()
                else
                    handle_special_property_mapping(interface, k, orm_obj, v:value())
                end
            end)
        end
    end)
end

local function synchronize_property(bus, device_path, orm_obj)
    log:notice('synchronize_property start, optical_device_path is %s', device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH, comm_defs.MDB_INTERFACE, 'GetObject', 'a{ss}sas',
        ctx.get_context_or_default(), device_path,
            {comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE,
            comm_defs.OPTICAL_MODULE_STATUS_INTERFACE,
            comm_defs.OPTICAL_MODULE_COOLING_INTERFACE,
            comm_defs.OPTICAL_MODULE_POWER_INTERFACE,
            comm_defs.OPTICAL_MODULE_DIAGNOSE_INTERFACE,
            comm_defs.OPTICAL_MODULE_VOLTAGE_INTERFACE,
            comm_defs.OPTICAL_MODULE_CURRENT_INTERFACE,
            comm_defs.OPTICAL_MODULE_CHANNEL_INTERFACE})
    local optical_device_obj, props
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            optical_device_obj = mdb.get_object(bus, device_path, interface)
            props = bus:call(service_name, device_path, comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
                pcall(function()
                    local ret, val = optical_device_obj:get_property(prop)
                    if ret ~= 0 then
                        log:error('get_property failed, prop=%s, device_path=%s', prop, device_path)
                        goto continue
                    end
                    if prop_table[prop] then
                        orm_obj[prop_table[prop]] = val
                    else
                        handle_special_property_mapping(interface, prop, orm_obj, val)
                    end
                    ::continue::
                end)
            end
        end
    end
end

local function create_optical_orm_object(port_id, network_adapter_id, card_object_name, port_object_name, object_name)
    local mdb_obj = c_optical_module.create_mdb_object({
        PortID = port_id,
        NetworkAdapterId = network_adapter_id,
        CardObjectName = card_object_name,
        PortObjectName = port_object_name,
        ObjectName = object_name,
        CreatedByDeviceObject = true -- 标识orm对象来源于设备树对象
    })
    c_factory.get_instance():create_object('OpticalModule', mdb_obj)
    local optical_orm_obj = c_optical_module.collection:find({
        PortID = port_id,
        NetworkAdapterId = network_adapter_id
    })
    return optical_orm_obj
end

-- 温度更新后，同步更新到设备树对象（用于iBMA获取到温度的场景）
function optical_module_mgmt:set_temperature_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_COOLING_INTERFACE)
    if not devcie_obj then
        return
    end
    devcie_obj.TemperatureCelsius = value
    log:info('%s port %s set_temperature_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 光模块在位信息更新后，同步更新到设备树对象（用于iBMA获取到光模块在位信息的场景）
function optical_module_mgmt:set_presence_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_presence_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_presence_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_presence_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.Presence = value
    log:notice('%s port %s set_presence_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 速率匹配更新后，同步更新到设备树对象（用于iBMA获取到速率匹配的场景）
function optical_module_mgmt:set_speed_match_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_speed_match_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_speed_match_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_STATUS_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_speed_match_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.SpeedMatch = value
    log:notice('%s port %s set_speed_match_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 类型匹配更新后，同步更新到设备树对象（用于iBMA获取到类型匹配的场景）
function optical_module_mgmt:set_type_match_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_type_match_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_type_match_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_STATUS_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_type_match_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.TypeMatch = value
    log:notice('%s port %s set_type_match_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 电压更新后，同步更新到设备树对象（用于iBMA获取到电压的场景）
function optical_module_mgmt:set_supply_voltage_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_supply_voltage_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_supply_voltage_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_VOLTAGE_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_supply_voltage_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.SupplyVoltage = value
    log:notice('%s port %s set_supply_voltage_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 电压下限阈值更新后，同步更新到设备树对象（用于iBMA获取到电压下限阈值的场景）
function optical_module_mgmt:set_voltage_lower_threshold_critical_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_voltage_lower_threshold_critical_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_voltage_lower_threshold_critical_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_VOLTAGE_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_voltage_lower_threshold_critical_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.VoltageLowerThresholdCritical = value
    log:notice('%s port %s set_voltage_lower_threshold_critical_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

-- 电压上限阈值更新后，同步更新到设备树对象（用于iBMA获取到电压上限阈值的场景）
function optical_module_mgmt:set_voltage_upper_threshold_critical_to_device_obj(optical_orm_obj, value)
    if not optical_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s port %s set_voltage_upper_threshold_critical_to_device_obj start, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
    local optical_device_path = optical_orm_obj.device_path
    if not optical_device_path or optical_device_path == '' then
        log:error('%s port %s set_voltage_upper_threshold_critical_to_device_obj failed, because optical_device_path is nil or empty',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, optical_device_path,
        comm_defs.OPTICAL_MODULE_VOLTAGE_INTERFACE)
    if not devcie_obj then
        log:error('%s port %s set_voltage_upper_threshold_critical_to_device_obj failed, because devcie_obj is nil, optical_device_path is %s',
            optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, optical_device_path)
        return
    end
    devcie_obj.VoltageUpperThresholdCritical = value
    log:notice('%s port %s set_voltage_upper_threshold_critical_to_device_obj success, value is %s',
        optical_orm_obj.NetworkAdapterId, optical_orm_obj.PortID, value)
end

function optical_module_mgmt:on_add_optical_device_obj()
    log:notice('on_add_optical_device_obj start')
    local self_instance = self
    comm_fun.set_interface_add_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE, function(path)
            self_instance:init_obj(path)
        end)
end

function optical_module_mgmt:on_del_optical_device_obj()
    log:notice('on_del_optical_device_obj start')
    local self_instance = self
    comm_fun.set_interface_del_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE, function(path)
            self_instance:del_obj(path)
        end)
end

local function register_parent(optical_orm_obj, port_orm_obj)
    optical_orm_obj.parent = port_orm_obj
    port_mgmt.get_instance():register_children(optical_orm_obj, port_orm_obj)
end

function optical_module_mgmt:get_orm_obj_by_device_path(optical_device_path)
    local optical_orm_obj = self.objects[optical_device_path]
    if not optical_orm_obj then
        log:error('there is not orm obj for optical device obj, optical_device_path is %s', optical_device_path)
        return
    end
    return optical_orm_obj
end

function optical_module_mgmt:init_obj(optical_device_path)
    -- 用pcall包裹，避免某一个光模块初始化失败导致循环结束
    pcall(function()
        if self.objects[optical_device_path] then
            return
        end
        log:notice('optical_module_mgmt init_obj start, optical_device_path is %s', optical_device_path)
        local port_device_path = comm_fun.get_parent_path(self.bus, optical_device_path)
        if not port_device_path then
            log:error('there is not parent device obj for optical module, optical_device_path is %s',
                optical_device_path)
            return
        end
        local port_orm_obj = port_mgmt.get_instance():get_orm_obj_by_device_path(port_device_path)
        if not port_orm_obj then
            log:error('there is not parent orm obj for optical module, optical_device_path is %s',
                optical_device_path)
            return
        end
        local object_name = comm_fun.get_object_name_by_device_path(self.bus, optical_device_path, 
            {from = "OpticalTransceiver", to = "OpticalModule"})
        if not object_name then
            log:error('get_object_name_by_device_path failed, optical_device_path is %s', optical_device_path)
            return
        end
        local optical_orm_obj = create_optical_orm_object(port_orm_obj.PortID, port_orm_obj.NetworkAdapterId,
            port_orm_obj.parent.ObjectName, port_orm_obj.ObjectName, object_name)
        if not optical_orm_obj then
            log:error('create_optical_orm_object failed, optical_device_path is %s', optical_device_path)
            return
        end
        if optical_orm_obj then
            log:notice('create_optical_orm_object success, optical_device_path is %s', optical_device_path)
            self.objects[optical_device_path] = optical_orm_obj
            -- 保存device_path到ORM对象中，供后续使用
            optical_orm_obj.device_path = optical_device_path
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            listen_device_obj_property_change(self.bus, self.sig_slot, optical_device_path, optical_orm_obj)
            -- 再将设备树对象属性赋值给对应资源树对象
            synchronize_property(self.bus, optical_device_path, optical_orm_obj)
            -- 光模块资源树对象创建后，建立和父对象网口的关系
            register_parent(optical_orm_obj, port_orm_obj)
            om_init.init(optical_orm_obj)
        end
    end)
end

function optical_module_mgmt:ctor(bus)
    self.bus = bus
    self.objects = {} -- 光模块设备树对象和资源树对象的映射关系
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function optical_module_mgmt:init()
    log:notice('optical_module_mgmt init')
    -- 先监听光模块设备树对象新增信号
    self:on_add_optical_device_obj()
    self:on_del_optical_device_obj()
    -- 获取所有的光模块设备树对象，创建对应的orm对象
    local optical_device_paths = comm_fun.get_all_device_paths(self.bus, comm_defs.CARD_DEVICE_PATH_PATTERN, 4,
        comm_defs.OPTICAL_MODULE_DEVICE_INTERFACE)
    for _, optical_device_path in pairs(optical_device_paths) do
        log:notice('get optical_device_path: %s', optical_device_path)
        self:init_obj(optical_device_path)
    end
end

function optical_module_mgmt:del_obj(path)
    log:notice('del_obj start, optical_device_path is %s', path)
    local orm_obj = self.objects[path]
    c_optical_module.collection:del_object(orm_obj)
    self.objects[path] = nil
end

return singleton(optical_module_mgmt)
